"use strict";

/**
	This constructor is used to create an default object of algorithm.
	@constructor
 */
var algorithm = {};

/**
	This array of algorithm holds a bunch of cellInfos to represent the result 
	of the sequence alignment.
 */
algorithm.array;

/**
	This string attribute of algorithm represents sequence A.
 */
algorithm.seqA;

/**
	This string attribute of algorithm represents sequence B.
 */
algorithm.seqB;

/**
	This array of algorithm is used to allocate all possible alignment pathes.
	In the array you find a bunch of number arrays.
 */
algorithm.pathArray;

/**
	This function calculates one cell of the array. It uses the given position x and y, the array and the sequences.
	
	@param {string} posX - position x
	@param {string} posY - position y
 */
algorithm.calculateCell=function(posX, posY){
	this.array[posX][posY] = new cellInfo();
	if(posX==0){
		this.array[posX][posY].value = posY;
		if(posY!=0){
			this.array[posX][posY].vertical = true;
		}
	}else if(posY==0){
		this.array[posX][posY].value = posX;
		this.array[posX][posY].horizontal = true;
	}else{
		var left = this.array[posX-1][posY].value+1;
		var up = this.array[posX][posY-1].value+1;
		var diag = this.array[posX-1][posY-1].value;
		if(this.seqA[posX] != this.seqB[posY]){
			diag++;
		}
		
		if(left <= up && left <= diag){
			this.array[posX][posY].value = left;
			this.array[posX][posY].horizontal = true;
		}
		if(up<=left && up <= diag){
			this.array[posX][posY].value = up;
			this.array[posX][posY].vertical = true;
		}
		if(diag<=left && diag <=up){
			this.array[posX][posY].value = diag;
			this.array[posX][posY].diagonal = true;
		}
	}
};

/**
	This function calculates the complete array. Therefore the sequence A and B have to be setted correctly.
	
	@param {function} callBack - callback function which is called when the calculation is finished
 */
algorithm.calculateArray=function(callBack){
	if(typeof(this.seqA) == "undefined" && typeof(this.seqB) == "undefined"){
		alert("Please set seqA and seqB first")
	}else{
		this.seqA = "-"+this.seqA;
		this.seqB = "-"+this.seqB;
		var lengthA = this.seqA.length;
		var lengthB = this.seqB.length;
		this.array = new Array(lengthA);
		for (var i = 0; i < lengthA; i++) {
			this.array[i] = new Array(lengthB);
		}
		
		// calculate array
		for(var i=0; i<lengthA; i++){
			for(var j=0; j<lengthB; j++){
				this.calculateCell(i,j);
			}
		}
		
		for(var i=0; i<lengthB; i++){
			var line = ""; 
			for(var j=0; j<lengthA; j++){
				line = line+" "+this.array[j][i].value;
			}
			if(debug)console.log(line);
		}
		
		for(var i=0; i<lengthB; i++){
			var line = ""; 
			for(var j=0; j<lengthA; j++){
				var pfeil = "";
				if(this.array[j][i].horizontal){
					pfeil += 'h';
				}else{
					pfeil += '-';
				}
				if(this.array[j][i].diagonal){
					pfeil += 'd';
				}else{
					pfeil += '-';
				}
				if(this.array[j][i].vertical){
					pfeil += 'v';
				}else{
					pfeil += '-';
				}
				line = line+" "+pfeil;
			}
			if(debug)console.log(line);
		}
		callBack();
	}
};

/**
	This function calculates all possible alignment pathes for the given sequences.
 */
algorithm.calculateMatrixPath=function(){
	var posX = this.array.length-1;
	var posY = this.array[0].length-1;
	var resultValue = {};
	resultValue.direction = "";
	resultValue.path = "";
	
	this.pathArray = new Array();
	var pathArrayIndex = this.createNewPath();
	this.routePath(posX, posY, pathArrayIndex);
	
	for(var i=0; i<this.pathArray.length; i++){
		for(var j=0; j<this.pathArray[i].length; j++){
			var aPosX = this.pathArray[i][j].posX;
			var aPosY = this.pathArray[i][j].posY;
			this.array[aPosX][aPosY].partOfPath.push(i);
		}
	}
	return resultValue;
}

/**
	This function can be used to show the path with the given index on the console or to collect the path length.
	
	@param {number} pathIndex - pathIndex (0 <= x < amount of pathes)
 */ 
algorithm.showPath=function(pathIndex){
	var lengthA = this.seqA.length;
	var lengthB = this.seqB.length;
	var pathLength = 0
	for(var i=0; i<lengthB; i++){
		var line = ""; 
		for(var j=0; j<lengthA; j++){
			var isPath = false;
			for(var k=0; k<this.array[j][i].partOfPath.length; k++){
				if(this.array[j][i].partOfPath[k] == pathIndex){
					isPath = true;
					pathLength++;
					break;
				}
			}
			if(isPath == true){
				line = line+" p";
			}else{
				line = line+" -";
			}
		}
		if(debug)console.log(line);
	}
	return pathLength;
}

/**
	This function is used to route a path. If another path is recognized it calls itself.
	
	@param {number} posX - position x in array
	@param {number} posY - position y in array
	@param {myIndex} myIndex - index of the path which is routed
 */
algorithm.routePath=function(posX, posY, myIndex){
	while(true){
		//if(debug)console.log("Route path: " + myIndex + "; current position: " + posX + ":" + posY);
		this.pathArray[myIndex].push({posX:posX, posY:posY});
		if(posX == 0 && posY == 0){
			break;
		}
		var result = this.calculateNextPathStep(posX, posY);
		for(var i=0; i<result.nextX.length; i++){
			if(i==0){
				posX = result.nextX[i];
				posY = result.nextY[i];
			}else{
				var pathArrayIndex = this.createNewPath(this.pathArray[myIndex]);
				this.routePath(result.nextX[i], result.nextY[i], pathArrayIndex);
			}
		}
	}
}

/**
	This function is used to create a new array in algorithm.pathArray . Therefore a
	new array is created and the original number array is copied the new one. 
	
	@param {array} originArray - original array which contents needs to be copied into the new one.
 */
algorithm.createNewPath=function(originArray){
	var newArray = new Array();
	if(originArray != null){
		for(var i=0; i<originArray.length; i++){
			newArray.push(originArray[i]);
		}
	}
	this.pathArray.push(newArray);
	return this.pathArray.length-1;
}

/**
	This function calculates the next step of the path.
	
	@param {number} posX - position x in array
	@param {number} posY - position y in array
 */
algorithm.calculateNextPathStep=function(posX, posY){
	var path = {};
	path.currentValue = this.array[posX][posY].value;
	path.nextX = new Array();
	path.nextY = new Array();
	if (this.array[posX][posY].diagonal==true){
		path.nextX.push(posX-1);
		path.nextY.push(posY-1);
	}
	if (this.array[posX][posY].horizontal==true){
		path.nextX.push(posX-1);
		path.nextY.push(posY);
	}
	if (this.array[posX][posY].vertical==true){
		path.nextX.push(posX);
		path.nextY.push(posY-1);
	}
	return path;
};

/**
	This function is used to calculate the alignment by the given path index. 
	Therefore the algorithm views how the path goes through the score alignment.
	
	@param {number} pathIndex - index of path
 */
algorithm.getAlignment=function(pathIndex){
	var pathAsString = this.getPathAsString(pathIndex);
	var sequenceA = this.seqB.substring(1,this.seqB.length);
	var sequenceB = this.seqA.substring(1,this.seqA.length);
	var result = new Array(2);
	var resultSeqA = new Array();
	var resultSeqB = new Array();
	var deleteAmount = 0;
	var insertAmount = 0;
	for(var i=0; i<pathAsString.length; i++){
		switch(pathAsString[i]){
			case 'v':
				resultSeqA.push(sequenceA[i-insertAmount]);
				resultSeqB.push("-");
				deleteAmount++;
				break;
			case 'h':
				resultSeqA.push("-");
				resultSeqB.push(sequenceB[i-deleteAmount]);
				insertAmount++;
				break;
			case 'd':
				resultSeqA.push(sequenceA[i-insertAmount]);
				resultSeqB.push(sequenceB[i-deleteAmount]);
				break;
			case 'D':
				resultSeqA.push(sequenceB[i-deleteAmount]);
				resultSeqB.push(sequenceB[i-deleteAmount]);
				break;
			default:
				if(debug)console.log("unknown character in pathAsString");
		}
	}
	result[0] = resultSeqA;
	result[1] = resultSeqB;
	return result;
}

/**
	This function is used to allocate how the path goes through the score matrix.
	
	@param {number} pathIndex - index of path
 */
algorithm.getPathAsString=function(pathIndex){
	var xPos=algorithm.array.length-1;
	var yPos=algorithm.array[0].length-1;
	var pathAsString = "";
	while(!(xPos==0 && yPos==0)){
		if(xPos>0 && yPos>0 && gui.cellIsPartOfPath(algorithm.array[xPos-1][yPos-1], pathIndex)){
			if(algorithm.array[xPos][yPos].value==algorithm.array[xPos-1][yPos-1].value){
				pathAsString = "D" + pathAsString;
			}else{
				pathAsString = "d" + pathAsString;
			}
			xPos = xPos-1;
			yPos = yPos-1;
		}else if(xPos>0 && gui.cellIsPartOfPath(algorithm.array[xPos-1][yPos], pathIndex)){
			pathAsString = "h" + pathAsString;
			xPos = xPos-1;
			yPos = yPos;
		}else{
			pathAsString = "v" + pathAsString;
			xPos = xPos;
			yPos = yPos-1;
		}
	}
	return pathAsString;
}
